#!/usr/bin/env python

# Copyright (C) 2012 Martin Helm <martin@mhelm.de>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You can get a copy of the GNU General Public License at
# <http://www.gnu.org/licenses/>.


import sys
import time
import os

missing_pkgs = """
Make sure you have support for D-Bus, Numpy, Matplotlib and Tkinter.
On rpm based GNU/Linux distributions you may need to install
    dbus-1-python
    python-numpy
    python-tk
    python-matplotlib
    python-matplotlib-tk
    pyttk (if you use python < 2.7)
or depending on your system similar named packages!"""

try:
    import Tkinter as Tk
    import ttk
    import tkFont
    import tkMessageBox
    import dbus
    import numpy as np
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
    from matplotlib.figure import Figure
except Exception, exc:
    print >> sys.stderr, exc
    print >> sys.stderr, missing_pkgs
    sys.exit(1)

NM_DEVICE_TYPE_802_11_WIRELESS = 2

HOME_DIR    = os.path.expanduser('~')
CONFIG_DIR  = HOME_DIR+"/.wifiplotter"
CONFIG_FILE = HOME_DIR+"/.wifiplotter/config.ini"

import ConfigParser

class Properties(ConfigParser.RawConfigParser, object):
    
    def __init__(self):
        super(Properties, self).__init__()
        if not os.path.exists(CONFIG_DIR):
            try:
                os.mkdir(CONFIG_DIR)
            except OSError:
                pass
            
        
    def load(self):       
        if not self.read(CONFIG_FILE):
            self._setDefaults()
            self.save()
        
        self._setDefaults() #populate missing items
        return self
    
    def save(self):
        with open(CONFIG_FILE, "w") as f:
            self.write(f)
    
    def _setDefaults(self):
        defaults = {"Gui":
                    {"geometry":"800x600+0+0", 
                     "autorefresh":5000},
                    "AccessPointTable":
                    {"color_even":"#f0f0ff",
                     "color_odd":"#e0e0ff"}}
 
        for section in defaults:
            if not self.has_section(section):
                self.add_section(section)
            
            for option in defaults[section]:
                if not self.has_option(section, option):
                    self.set(section, option, defaults[section][option]) 


class Wifi(object):
    """Handle wifi devices and access points via the dbus interface to
    Networkmanager"""
    
    NM          = "org.freedesktop.NetworkManager"
    NM_PATH     = "/org/freedesktop/NetworkManager"
    NM_DEVICE   = "org.freedesktop.NetworkManager.Device"
    NM_WL       = "org.freedesktop.NetworkManager.Device.Wireless"
    NM_AP       = "org.freedesktop.NetworkManager.AccessPoint"
    NM_AC       = "org.freedesktop.NetworkManager.Connection.Active"
    DBUS_PROP   = "org.freedesktop.DBus.Properties"
    
    # prepare lookup table for 20MHz bandwidth OFDM channels
    cx = [-30, -20, -11, -9, 9, 11, 20, 30]   # LUT for MHz values
    cy = [-45, -28, -20, 0, 0, -20, -28, -45] # LUT for dBr according to IEEE 802.11
    cxx = np.arange(-30, 31, 1)               # full range of MHz values 
    cyy = np.interp(cxx, cx, cy)              # linear interpolation of cy
    OFDM20 = np.power(10.0, cyy/10.0)         # convert dBr to attenuation factors

    # prepare lookup table for 40MHz bandwidth OFDM channels
    cx = [-60, -40, -21, -19, 19, 21, 40, 60] # LUT for MHz values
    cy = [-45, -28, -20, 0, 0, -20, -28, -45] # LUT for dBr according to IEEE 802.11
    cxx = np.arange(-60, 61, 1)               # full range of MHz values 
    cyy = np.interp(cxx, cx, cy)              # linear interpolation of cy
    OFDM40 = np.power(10.0, cyy/10.0)         # convert dBr to attenuation factors

    del cx, cy, cxx, cyy                      # cleanup
    
    max_qual = {"level":146, "qual":70}
    
    # map MHz to channels for the 2.4 GHz range
    channels24 = {2412:1, 2417:2, 2422:3, 2427:4, 2432:5, 2437:6, 2442:7, 2447:8, 2452:9,
                  2457:10, 2462:11, 2467:12, 2472:13, 2484:14}
    
    # map MHz to channels for the 5 GHz range
    channels5 = {5180:36, 5200:40, 5220:44, 5240:48, 5260:52, 5280:56, 5300:60, 5320:64,
                 5500:100, 5520:104, 5540:108, 5560:112, 5580:116, 5600:120, 5620:124,
                 5640:128, 5660:132, 5680:136, 5700:140, 5735:147, 5745:149, 5755:151,
                 5765:153, 5775:155, 5785:157, 5805:161, 5825:165, 5835:167}
    
    @staticmethod
    def rel2dbm(r):
        # TODO: replace by call to iw_get_range_info from libiw for correct values 
        # for all devices
        return r*Wifi.max_qual["qual"] + Wifi.max_qual["level"] - 256
    
    @staticmethod    
    def rel2mw(r):
        return np.power(10.0, Wifi.rel2dbm(r)/10.0)

    @staticmethod    
    def mw2dbm(mw):
        if mw > 0.0:
            return np.log10(mw)*10.0
        else:
            return -np.Inf # this avoids a silly division by zero warning

    def __init__(self):
        self.bus = dbus.SystemBus()
        self.nm = self.bus.get_object(Wifi.NM, Wifi.NM_PATH)
        self.devices = self.nm.GetDevices(dbus_interface=Wifi.NM)
        self.current_device = None
        self.device_list = []
        self.active_accesspoint = None
        self.access = None

    def scanDevices(self):
        success = False
        self.current_device = None
        self.device_list = []
        for path in self.devices:
            device = self.bus.get_object(Wifi.NM, path)
            device_props = dbus.Interface(device, dbus_interface=Wifi.DBUS_PROP)
            props = device_props.GetAll(Wifi.NM_DEVICE)
            if props["DeviceType"] == NM_DEVICE_TYPE_802_11_WIRELESS:
                interface = props.get("IpInterface", None)
                driver = props.get("Driver", None)
                self.device_list.append([device, "%s (%s)" % (interface, driver)])
                
        if len(self.device_list) > 0:
            self.current_device = self.device_list[0][0]
            success = True
            
        return success
    
    def setCurrentDevice(self, device):
        update = device in self.device_list
        if update:
            self.current_device = device

        return update
    
    def getActiveAccessPoint(self):
        device_props = dbus.Interface(self.current_device, 
                                      dbus_interface=Wifi.DBUS_PROP).GetAll(Wifi.NM_WL)
                                      
        self.active_accesspoint = device_props["ActiveAccessPoint"]
        self.active_bitrate = int(device_props["Bitrate"])
        
        return self.active_accesspoint, self.active_bitrate
           
    def scanAccessPoints24(self, scan=True):
        return self.scanAccessPoints(Wifi.channels24, scan)
     
    def scanAccessPoints5(self, scan=True): 
        return self.scanAccessPoints(Wifi.channels5, scan)

    def scanAccessPoints(self, channels, scan): 
        if self.current_device == None:
            tkMessageBox.showwarning("scanAccessPoints", 
                                     "No wifi device for scanning selected!")
            return []
        
        if self.access == None or self.active_accesspoint == None or scan:
            self.getActiveAccessPoint()
            self.access = self.current_device.GetAccessPoints(dbus_interface=Wifi.NM_WL)

        ap_list = [] # list of wifi access points 
        for path in self.access:
            active = (path == self.active_accesspoint)
            active_text = "yes" if active else ""
            ap = self.bus.get_object(Wifi.NM, path)
            ap_props = dbus.Interface(ap, dbus_interface=Wifi.DBUS_PROP)
            ap_prop = ap_props.GetAll(Wifi.NM_AP)
            
            wifi = {"Ssid":None, "Channel":None, "Strength":None, "Active":active_text}
            for key in ap_prop:
                if key == "Ssid":
                    wifi[str(key)] = "".join(map(str,ap_prop[key]))
                elif key == "Frequency":
                    wifi["Channel"] = channels.get(ap_prop[key], None)
                    wifi["Frequency"] = int(ap_prop[key])
                elif key ==  "Strength":
                    wifi["Strength"] = ap_prop[key]/100.0
                    wifi["dBm"] = Wifi.rel2dbm(wifi["Strength"])
                else:
                    wifi[str(key)] = str(ap_prop[key])
            
            if active: wifi["MaxBitrate"] = self.active_bitrate
            
            if not wifi["Channel"] == None and not wifi["Strength"] == None:
                ap_list.append(wifi)
            
        return ap_list

    def getSpectrum24(self, ap_list):
        return self.getSpectrum(ap_list, Wifi.channels24)
    
    def getSpectrum5(self, ap_list):
        return self.getSpectrum(ap_list, Wifi.channels5)
    
    def getSpectrum(self, ap_list, channels):
        channels = sorted(channels)
        ch = np.zeros(max(channels)-min(channels)+1)
        single_ch = np.zeros((max(channels)-min(channels)+1, len(ap_list)))

        index = 0
        for ap in ap_list:
            # print just for debugging purposes
            #print ap
            w = Wifi.rel2mw(ap["Strength"])
            ch_index = ap["Frequency"] - min(channels)
            ch[ch_index] += w
            single_ch[ch_index, index] = w
            index += 1
        
        spect_single_mw = []
        spect_single_dbm = []
        for index in range(single_ch.shape[1]):
            spect = np.convolve(single_ch[:,index], Wifi.OFDM20, mode="same")
            spect_single_mw.append(spect)
            spect_single_dbm.append(map(Wifi.mw2dbm, spect))
        
        spect_total_mw = np.convolve(ch, Wifi.OFDM20, mode="same")
        spect_total_dbm = map(Wifi.mw2dbm, spect_total_mw)

        return spect_single_mw, spect_single_dbm, spect_total_mw, spect_total_dbm


class Table(object):
    """use a ttk.TreeView as a multicolumn ListBox"""
    def __init__(self, container, headers, conversions, item_list):
        self.tree = None
        self.container = container
        self.headers = headers
        self.conversions = conversions
        self.tree = None
        self.item_list = item_list
        self.sort_col = headers[0]
        self.sort_descending = 0
        self.col_conversions = dict(zip(headers, conversions))
        self._setup_widgets()
        self.update(item_list)
        
    def update(self, item_list, update=False):
        self.item_list = item_list
        self._build_tree(update=update)
    
    def _setup_widgets(self):
        self.tree = ttk.Treeview(columns=self.headers, show="headings")
        vsb = ttk.Scrollbar(orient=Tk.VERTICAL, command=self.tree.yview)
        #hsb = ttk.Scrollbar(orient=Tk.HORIZONTAL, command=self.tree.xview)
        self.tree.configure(yscrollcommand=vsb.set) #, xscrollcommand=hsb.set)
        self.tree.grid(column=0, row=0, sticky='nsew', in_=self.container)
        vsb.grid(column=1, row=0, sticky='ns', in_=self.container)
        #hsb.grid(column=0, row=1, sticky='ew', in_=self.container)
        self.container.grid_columnconfigure(0, weight=1)
        self.container.grid_rowconfigure(0, weight=1)
        
    def _build_tree(self, update=False):
        if not update:
            for col in self.headers:
                self.tree.heading(col, text=col.title(),
                                  command=lambda c=col: self.sortby(c, self.sort_descending))
                # adjust the column's width to the header string
                self.tree.column(col, width=tkFont.Font().measure(col.title()),
                                 anchor="center")
        
        global properties    
        color_even = properties.get("AccessPointTable", "color_even")
        color_odd = properties.get("AccessPointTable", "color_odd")
        
        if update:
            data = [child for child in self.tree.get_children('')]
            for item in data: self.tree.delete(item)
            
        for i, item in enumerate(self.item_list):
            tag = "even" if i%2 == 0 else "odd"
            values=[]
            for j, key in enumerate(self.headers):
                try:
                    values.append(self.conversions[j](item[key]))
                except Exception, exc:
                    print exc
                    values.append(0)
            
            self.tree.insert('', 'end', values=values, tags=tag)
            
            # adjust column's width if necessary to fit each value
            for ix, val in enumerate(values):
                col_w = tkFont.Font().measure(str(val))
                if self.tree.column(self.headers[ix],width=None) < col_w:
                    self.tree.column(self.headers[ix], width=col_w)
                    
            self.tree.tag_configure("even", background=color_even)
            self.tree.tag_configure("odd", background=color_odd)
            
        self.sort()

    def sort(self):
        # grab values to sort
        data = [(self.col_conversions[self.sort_col](self.tree.set(child, self.sort_col)), child) 
                for child in self.tree.get_children('')]
        
        data.sort(reverse=self.sort_descending)
        for ix, item in enumerate(data):
            tag = "even" if ix%2 == 0 else "odd"
            self.tree.item(item[1], tags=tag)
            self.tree.move(item[1], '', ix)
                    
    def sortby(self, col, descending):
        """sort tree contents when a column header is clicked on"""
        self.sort_col=col
        self.sort_descending=descending
        self.sort()
        # switch the heading so it will sort in the opposite direction
        self.tree.heading(col, 
                          command=lambda col=col: self.sortby(col, int(not descending)))


class AccessPointTable(object):

    def __init__(self, parent, ap_list24, ap_list5, update_plot24, update_plot5):
        self.ap_list24 = ap_list24
        self.ap_list5 = ap_list5
        self.update_plot24 = update_plot24
        self.update_plot5 = update_plot5
        
        self.notebook = ttk.Notebook(parent)
        self.notebook.pack(side=Tk.BOTTOM, fill=Tk.BOTH, expand=Tk.YES)
        
        self.container24 = ttk.Frame(parent)
        self.container24.pack(fill=Tk.X, expand=1)
        self.container5 = ttk.Frame(parent)
        self.container5.pack(fill=Tk.X, expand=1)
        
        self.notebook.add(self.container24, text="2.4 GHz", underline=0) 
        self.notebook.add(self.container5, text="5 GHz", underline=0)

        self.keys = ["Ssid", "Active", "HwAddress", "Frequency", 
                     "Channel", "MaxBitrate", "dBm"]
        self.conversions = [lambda x: x, 
                            lambda x: x, 
                            lambda x: x, 
                            lambda x: int(x), 
                            lambda x: int(x), 
                            lambda x: int(x),
                            lambda x: float(x)]

        self.table24 = Table(self.container24, self.keys, self.conversions, self.ap_list24) 
        self.table5 = Table(self.container5, self.keys, self.conversions, self.ap_list5) 
        
        self.old_tablecol = 0
        self.table24.tree.bind("<Motion>", lambda ev: self.showTooltip(ev, self.table24))
        self.table5.tree.bind("<Motion>", lambda ev: self.showTooltip(ev, self.table5))

        self.notebook.bind_all("<<NotebookTabChanged>>", self.tabChanged)
        
    def update(self, ap_list24, ap_list5, update=False):
        self.ap_list24 = ap_list24
        self.ap_list5 = ap_list5
        self.table24.update(self.ap_list24, update=update)
        self.table5.update(self.ap_list5, update=update)
        
        index = self.notebook.index("current")
        if index == 0:
            self.update_plot24()
        elif index == 1:
            self.update_plot5()

       
    def tabChanged(self, event):
        index = self.notebook.index("current")
        if index == 0:
            self.update_plot24()
        elif index == 1:
            self.update_plot5()
    
    def showTooltip(self, event, table):
        if not table.tree.identify("region", event.x, event.y) == "heading":
            return
        
        try:
            col = int(table.tree.identify_column(event.x)[1:])
        except:
            col = 0
        
        if not self.old_tablecol == col:
            print col      
        
        self.old_tablecol = col

class SpectrumPlot(object):

    def __init__(self, spect_total, spect_single, is_dbm=True, freq=24, master=None):
        self.master = master
        self.figure = Figure(figsize=(5,4), dpi=100)
        
        self.update(spect_total, spect_single, is_dbm, freq, refresh=False)

        self.canvas = FigureCanvasTkAgg(self.figure, master=self.master)
        self.canvas.show()
        self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)

        self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.master)
        self.toolbar.update()
        self.canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
        
        self.refresh()
    
    def update(self, spect_total, spect_single, is_dbm=True, freq=24, refresh=True):
        self.freq = freq
        self.is_dbm = is_dbm
        self.spect_total = spect_total
        self.spect_single = spect_single

        if refresh: self.refresh()
        
    def refresh(self):
        self.figure.clf()
        self.plt_total = self.figure.add_subplot(211)
        self.plt_single = self.figure.add_subplot(212)
        self.figure.subplots_adjust(left=0.12, bottom=0.15, right=0.90, top=0.90, wspace=0.20, hspace=0.40)        
        
        if self.freq == 24:
            self.plt_total.set_title("Power spectrum for 2.4 GHz range")
            if self.is_dbm:
                self.plot_spectrum24(self.plt_total, (self.spect_total,), 
                                     "Total signal (dBm)", [-115.0, -20.0])
                self.plot_spectrum24(self.plt_single, self.spect_single, 
                                     "Individual signals (dBm)", [-115.0, -20.0])
            else:
                self.plot_spectrum24(self.plt_total, ((self.spect_total,)), 
                                     "Total signal (mW)")
                self.plot_spectrum24(self.plt_single, self.spect_single,
                                     "Individual signals (mW)")
        elif self.freq == 5:
            self.plt_total.set_title("Power spectrum for 5 GHz range")
            if self.is_dbm:
                self.plot_spectrum5(self.plt_total, (self.spect_total,), 
                                     "Total signal (dBm)", [-115.0, -20.0])
                self.plot_spectrum5(self.plt_single, self.spect_single, 
                                     "Individual signals (dBm)", [-115.0, -20.0])
            else:
                self.plot_spectrum5(self.plt_total, ((self.spect_total,)), 
                                     "Total signal (mW)")
                self.plot_spectrum5(self.plt_single, self.spect_single, 
                                     "Individual signals (mW)")
        
        self.canvas.show()

    def plot_spectrum24(self, plt, spectrum, y_label, y_range=None):
        self.plot_spectrum(plt, spectrum, y_label, y_range, Wifi.channels24)
        
    def plot_spectrum5(self, plt, spectrum, y_label, y_range=None):
        self.plot_spectrum(plt, spectrum, y_label, y_range, Wifi.channels5, 90)
    
    def plot_spectrum(self, plt, spectrum, y_label, y_range, channels, rotation=0):
        col = ["b", "y", "m", "c", "r", "g"]
        if len(spectrum) == 1: col = ["r"]
        
        _channels = sorted(channels)
        x_range = np.arange(min(_channels), max(_channels)+1)
        x_ticks = [key for key in _channels]
        x_ticklabels = [channels[key] for key in _channels]
    
        index = 0
        for spect in spectrum:
            plt.plot(x_range, spect, color=col[index % 6])
            index += 1
            
        if y_range == None:
            plt.ticklabel_format(style='sci',axis='y',scilimits=(0,0))
        else:
            plt.set_ylim(*y_range)
        
        plt.set_xlim(x_range[0], x_range[-1])
        plt.set_xticks(x_ticks)
        plt.set_xticklabels(x_ticklabels, rotation=rotation)
        plt.grid()
        plt.set_xlabel("Channel")
        plt.xaxis.get_label().set_fontsize(9)
        plt.set_ylabel(y_label)
        plt.yaxis.get_label().set_fontsize(9)
    
        for label in plt.xaxis.get_ticklabels():
            label.set_fontsize(8)
            
        for label in plt.yaxis.get_ticklabels():
            label.set_fontsize(8)
        

class Dialog(Tk.Toplevel, object):
    
    def __init__(self, parent):
        super(Dialog, self).__init__(parent)
        self.parent = parent
    
    def show(self):
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self.focus_set()
        self.grab_set()
        self.geometry("+%d+%d" % (self.parent.winfo_rootx()+self.parent.winfo_width()/3, 
                                 self.parent.winfo_rooty()+self.parent.winfo_height()/3))

        self.mainloop()
        self.destroy()


class WifiPlotter(object):
    
    def __init__(self):
        global properties
        properties = Properties().load()
        
        self.root = Tk.Tk()
        self.root.geometry(properties.get("Gui", "geometry"))
        self.root.wm_title("Wifiplotter")
        self.root.protocol("WM_DELETE_WINDOW", self._quit)
    
        self.frame = ttk.Frame(self.root)
        self.frame.pack_propagate(0)

        self.wifi = Wifi()
        if not self.wifi.scanDevices():
            print >> sys.stderr, "No wireless network adapters found"
            tkMessageBox.showerror("Wifi.scanDevices", 
                                   "No wireless network adapters found!")
            sys.exit(1) # brute force, no pardon
        
        self.createMenubar()
            
        self.apt = None
        self.plot = None
        self.updateAccessPoints()
        
        self.frame.pack(fill=Tk.BOTH, expand=1)
        self.root.after(properties.get("Gui", "autorefresh"), 
                        lambda: self.updateAccessPoints(update=True))
        
        Tk.mainloop()
    
    def changeDevice(self, device):
        self.wifi.setCurrentDevice(device)
        self.updateAccessPoints(update=True)
        
    def createMenubar(self):
        self.menubar = Tk.Menu(self.root)
        self.devices = self.wifi.device_list
        
        self.filemenu = Tk.Menu(self.menubar, tearoff=0)
        self.filemenu.add_command(label="Exit", underline=1, 
                                  command=lambda: self._quit(),
                                  accelerator="Ctrl+Q")
        self.menubar.add_cascade(label="File", underline=0, menu=self.filemenu)
    
        self.devicemenu = Tk.Menu(self.menubar, tearoff=0)
        self.dev_radio = Tk.StringVar()
        self.dev_radio.set(self.devices[0][1])
        for device in self.devices:
            self.devicemenu.add_radiobutton(label=device[1], variable=self.dev_radio, 
                                            value=device[1],
                                            command=(lambda: self.changeDevice(device[0])))

        self.menubar.add_cascade(label="Devices", underline=0, menu=self.devicemenu)
    
        self.viewmenu = Tk.Menu(self.menubar, tearoff=0)
        
        self.viewmenu.refresh = Tk.Menu(self.viewmenu, tearoff=0)
        self.refresh_radio = Tk.IntVar()
        self.refresh_radio.set(properties.get("Gui", "autorefresh"))
        for sec in [2, 5, 10, 15]:
            self.viewmenu.refresh.add_radiobutton(label=str(sec)+" sec", 
                                              variable=self.refresh_radio,
                                              value=sec*1000,
                                              command = lambda: self.setRefresh())
        
        self.viewmenu.add_cascade(label= "Refresh rate", menu=self.viewmenu.refresh)
        
        self.menubar.add_cascade(label="View", underline=0, menu=self.viewmenu)
        
        self.helpmenu = Tk.Menu(self.menubar, tearoff=0)
        self.helpmenu.add_command(label="About", underline=0, 
                                  command=(lambda: self.about(self.frame)))

        self.helpmenu.add_command(label="License", underline=0, command=self.show_license)
        self.menubar.add_cascade(label="Help", underline=0, menu=self.helpmenu)
    
        self.root.config(menu=self.menubar)
        self.root.bind_all("<Control-q>", lambda event: self._quit())
        
    def setRefresh(self):
        global properties
        properties.set("Gui", "autorefresh", self.refresh_radio.get())

    def updateAccessPoints(self, update=False):
        if update: # schedule next run
            global properties
            self.root.after(properties.get("Gui", "autorefresh"), 
                            lambda: self.updateAccessPoints(update=True))

        self.wifi_list24 = self.wifi.scanAccessPoints24()
        self.single_mw24, self.single_dbm24, self.total_mw24, self.total_dbm24 = \
            self.wifi.getSpectrum24(self.wifi_list24)
        self.wifi_list5 = self.wifi.scanAccessPoints5(scan=False)
        self.single_mw5, self.single_dbm5, self.total_mw5, self.total_dbm5 = \
            self.wifi.getSpectrum5(self.wifi_list5)
        
        if self.plot == None:
            self.plot = SpectrumPlot(self.total_dbm24, self.single_dbm24, 
                                     freq=24, master=self.frame)
    
        def update24():
            if not update24.first:
                self.plot.update(self.total_dbm24, self.single_dbm24, freq=24)
            update24.first = False
        #this is really convoluted, there must be a better way to avoid this drawing problem
        update24.first = True
        
        def update5():
            self.plot.update(self.total_dbm5, self.single_dbm5, freq=5)
        
        if self.apt == None:
            self.apt = AccessPointTable(self.frame, self.wifi_list24, 
                                        self.wifi_list5, update24, update5)
            
            ttk.Notebook.enable_traversal(self.apt.notebook)
        else:
            self.apt.update(self.wifi_list24, self.wifi_list5, update=True)
            
        
    def about(self, parent):
        text = \
"""Wifiplotter 0.5 (c) 2012 Martin Helm

License: GPL v3

Credits:
Malcolm J. Lewis for patches, tests and support
Icons by Oliver (schollidesign.deviantart.com)
openSUSE forums for testing and discussion"""
        
        dlg = Dialog(parent)
        dlg.title("About Wifiplotter")
        dlg.configure(background="lightgray")
        
        label = ttk.Label(dlg, text=text)
        label.configure(background="lightgray")
        label.pack(padx=10, pady=10)
        
        ttk.Button(dlg, text="Ok", command=dlg.quit).pack(padx=3, pady=3)
        
        dlg.show()
        
    def show_license(self):
        import webbrowser
        webbrowser.open("http://www.gnu.org/licenses/gpl.html")
    
    def _quit(self):
        global properties
        properties.set("Gui", "geometry", self.root.winfo_geometry())
        properties.save()
        self.root.quit()
            

def main():
    WifiPlotter()

if __name__ == "__main__":
    main()
    